package ubadb.core.components.bufferManager.bufferPool.replacementStrategies.touchCount;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import ubadb.core.common.Page;
import ubadb.core.components.bufferManager.bufferPool.BufferFrame;
import ubadb.core.components.bufferManager.bufferPool.replacementStrategies.PageReplacementStrategy;
import ubadb.core.exceptions.PageReplacementStrategyException;

public class TouchCountReplacementStrategy implements PageReplacementStrategy {
	
	private Integer _db_percent_hot_default = 50;
	private Double _db_aging_touch_time = 3d;
	private Integer _db_aging_hot_criteria = 2;
	private Integer _db_aging_stay_count = 0;
	private Integer _db_aging_cool_count = 1;
	
	private LinkedList<TouchCountBufferFrame> hotList = new LinkedList<TouchCountBufferFrame>();
	private LinkedList<TouchCountBufferFrame> coldList = new LinkedList<TouchCountBufferFrame>();
	
	public TouchCountReplacementStrategy() {
		super();
	}
	
	public TouchCountReplacementStrategy(Integer _db_percent_hot_default,
			Double _db_aging_touch_time, Integer _db_aging_hot_criteria,
			Integer _db_aging_stay_count, Integer _db_aging_cool_count) {
		super();
		this._db_percent_hot_default = _db_percent_hot_default;
		this._db_aging_touch_time = _db_aging_touch_time;
		this._db_aging_hot_criteria = _db_aging_hot_criteria;
		this._db_aging_stay_count = _db_aging_stay_count;
		this._db_aging_cool_count = _db_aging_cool_count;
	}

	@Override
	public BufferFrame findVictim(Collection<BufferFrame> bufferFrames) throws PageReplacementStrategyException {
		TouchCountBufferFrame victim = null;
		// Se pasan de la lista fría (coldList) a la lista caliente (hotList)
		// aquellos búffers cuyo touchCount (o "popularidad") sea mayor o igual a "_db_aging_hot_criteria".
		Iterator<TouchCountBufferFrame> it = coldList.iterator();
		while (it.hasNext()) {
			TouchCountBufferFrame bufferFrame = it.next();
			if (bufferFrame.getTouchCount() >= _db_aging_hot_criteria) {
				it.remove();
				if (_db_aging_stay_count >= _db_aging_hot_criteria) {
					bufferFrame.setTouchCount(bufferFrame.getTouchCount() / 2);	
				} else {
					bufferFrame.setTouchCount(_db_aging_stay_count);
				}
				hotList.addLast(bufferFrame);
			}
		}
		// Se balancea según el valor del parámetro "_db_percent_hot_default".
		balanceCharge();
		
		// Se recorre la lista fría (coldList) buscando el primer búffer con pin = 0, 
		// si no hay ninguno se realiza la búsqueda en la lista caliente (hotList).
		it = coldList.iterator();
		while (it.hasNext() && victim == null) {
			TouchCountBufferFrame frame = it.next();
			if (frame.canBeReplaced()) {
				victim = frame;
				it.remove();
			}
		}
		if (victim == null) {
			it = hotList.iterator();
			while (it.hasNext() && victim == null) {
				TouchCountBufferFrame frame = it.next();
				if (frame.canBeReplaced()) {
					victim = frame;
					it.remove();
				}
			}
		}
		if(victim == null)
			throw new PageReplacementStrategyException("No page can be removed from pool");
		else
			return victim;
	}

	private void balanceCharge() {
		Integer totalSize = coldList.size() + hotList.size();
		Integer targetHotSize = (totalSize * _db_percent_hot_default) / 100;
		while (targetHotSize < hotList.size()) {
			TouchCountBufferFrame frame = hotList.removeFirst();
			frame.setTouchCount(_db_aging_cool_count);
			coldList.addLast(frame);
		}
	}

	@Override
	public BufferFrame createNewFrame(Page page) {
		TouchCountBufferFrame newFrame = new TouchCountBufferFrame(page, _db_aging_touch_time); 
		midpointInsertion(newFrame);
		return newFrame;
	}

	private void midpointInsertion(TouchCountBufferFrame newFrame) {
		// Tengo que insertar en el final de la coldList;
		coldList.add(newFrame);
	}
	
}